package com.bjy.qa.agent.tools.http;

import okhttp3.Response;
import okhttp3.*;
import okhttp3.logging.HttpLoggingInterceptor;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class HttpHelper {
    private static final Logger logger = LoggerFactory.getLogger(HttpHelper.class);

    private static String charset = "UTF-8"; // 默认参数字符集
    private static PostType postType = PostType.FORM; // 默认 post 数据类型
    private static int timeout = 30; // 默认超时时间
    private static Map<String, String> requestHeaders = new HashMap<>();

    private volatile static HttpHelper instance; // 防止多个线程同时访问

    /**
     * 增加一个 Request Header
     * @param key key
     * @param value value
     * @return
     */
    public static boolean addRequestHeader(String key, String value) {
        HttpHelper.requestHeaders.put(key, value);
        return true;
    }

    /**
     * 构造 Headers
     * @param headers headers map
     * @return
     */
    public static Headers headersBuilder(Map<String, String> headers) {
        Headers.Builder hedersBuilder = new Headers.Builder();
        if (headers != null) {
            headers.forEach((key, value) -> {
                hedersBuilder.add(key, value);
            });
        }
        return hedersBuilder.build();
    }

    private HttpHelper() {
    }

    /**
     * 单例模式
     * @return
     */
    public static HttpHelper getInstance() {
        if (instance == null) {
            synchronized (HttpHelper.class) {
                if (instance == null) {
                    instance = new HttpHelper();
                }
            }
        }
        return instance;
    }

    /**
     * 设置字符集
     * @param charset 字符集
     */
    public static void setCharset(String charset) {
        HttpHelper.charset = charset;
    }

    /**
     * 返回字符集
     * @return 字符集
     */
    public static String getCharset() {
        return HttpHelper.charset;
    }

    /**
     * 设置媒体类型
     * @param postType 媒体类型
     */
    public static void setMediaType(PostType postType) {
        HttpHelper.postType = postType;
    }

    /**
     * 设置默认超时时间
     * @param second 超时时间，秒
     */
    public static void setTimeout(int second) {
        timeout = second;
    }

    /**
     * http请求回调类
     * @param <T>
     */
    public interface Callback<T> {
        /**
         * 成功回调此方法
         *
         * 当远程服务器成功返回 HTTP 响应时回调此方法。在 ResponseBody 关闭之前，可以通过 response.body 得到响应的正文。
         * 请注意：传输层成功（TCP层成功过，可以收到 HTTP code、headers 和 body）并不代表应用层成功，HTTP code 可能返回 404 或 500
         * @param result
         * @throws IOException
         */
        public void onResponse(T result) throws IOException;

        /**
         * 失败回调此方法
         *
         * 由于链接取消或超时而无法返回而失败，比如：请求发到了服务器，但由于网络等原因连接丢失
         * @param e IOException
         */
        public void onFailure(IOException e);
    }

    /**
     * 返回全链接
     * url地址加上参数，例如：http://www.baidu.com/s?wd=aa
     * @param url url地址
     * @param params 参数
     * @return 包括参数的url链接字符串
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    private String getFullUrl(String url, Params params) throws UnsupportedEncodingException {
        if (params==null || params.size()==0) {
            return url;
        } else {
            StringBuffer stringBuffer = new StringBuffer();
            Set<Map.Entry<String, Object>> entrys = params.entrySet();
            for (Map.Entry entry : entrys) {
                if (stringBuffer.length() > 0) {
                    stringBuffer.append("&");
                }

                if (entry.getValue() instanceof String) {
                    stringBuffer.append(entry.getKey() + "=" + URLEncoder.encode(entry.getValue().toString(), charset));
                } else {
                    stringBuffer.append(entry.getKey() + "=" + entry.getValue());
                }

            }
            stringBuffer.insert(0, "?");
            stringBuffer.insert(0, url);
            return stringBuffer.toString();
        }
    }

    /**
     * 返回 Form 表单的 Http Request
     * @param url url地址
     * @param urlParams url 地址中的参数
     * @param bodyParams body中的参数
     * @return String
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    private Request buildFormPostRequest(String url, Params urlParams, Params bodyParams) throws UnsupportedEncodingException {
        FormBody.Builder builder = new FormBody.Builder();
        if (bodyParams!=null && bodyParams.size()>0) {
            Set<Map.Entry<String, String>> entrys = bodyParams.entrySet();
            for (Map.Entry entry : entrys) {
                builder.addEncoded(entry.getKey().toString(), URLEncoder.encode(entry.getValue().toString(), charset));
            }
        }
        return new Request.Builder().url(getFullUrl(url, urlParams)).headers(headersBuilder(requestHeaders)).post(builder.build()).build();
    }

    /**
     * 返回 Json 数据的 Http Request
     * @param url url地址
     * @param urlParams url 地址中的参数
     * @param bodyParams body中的参数
     * @return String
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    private Request buildJsonPostRequest(String url, Params urlParams, Params bodyParams) throws UnsupportedEncodingException {
        String fullUrl = getFullUrl(url, urlParams);
        RequestBody requestBody = RequestBody.Companion.create(bodyParams.toJsonString(), MediaType.JSON.getMediaType());

        return new Request.Builder().url(fullUrl).headers(headersBuilder(requestHeaders)).post(requestBody).build();
    }

    /**
     * 得到 OkHttpClient 对象
     * @return OkHttpClient
     */
    private OkHttpClient getOkHttpClient() {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                logger.debug(message);
            }
        });
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); //设置日志打印级别（所有日志，包括 request、response、header 和 body）

        OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
                .readTimeout(timeout, TimeUnit.SECONDS)
                .writeTimeout(timeout, TimeUnit.SECONDS)
                .connectTimeout(timeout, TimeUnit.SECONDS)
                .addInterceptor(loggingInterceptor);
        return clientBuilder.build();
    }

    /**
     * get 异步请求
     * @param url url地址
     * @param callback 回调
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    public void getAsync(String url, Callback callback) throws UnsupportedEncodingException {
        this.getAsync(url, null, callback);
    }

    /**
     * get 异步请求
     * @param url url地址
     * @param params 参数
     * @param callback 回调
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    public void getAsync(String url, Params params, Callback callback) throws UnsupportedEncodingException {
        OkHttpClient client = getOkHttpClient();
        Request request = new Request.Builder()
                .url(getFullUrl(url, params))
                .headers(headersBuilder(requestHeaders))
                .build();
        Response response = null;

        client.newCall(request).enqueue(new okhttp3.Callback() {
            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                callback.onResponse(new com.bjy.qa.agent.tools.http.Response(request.headers(), response.code(), response.headers(), response.body()));
            }

            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                callback.onFailure(e);
            }
        });
    }

    /**
     * get 同步请求
     * @param url url地址
     * @param <T>
     * @return
     * @throws IOException IOException
     */
    public <T> T get(String url) throws IOException {
        return get(url, null);
    }

    /**
     * get 同步请求
     * @param url url地址
     * @param params 参数
     * @param <T>
     * @return
     * @throws IOException IOException
     */
    public <T> T get(String url, Params params) throws IOException {
        OkHttpClient client = getOkHttpClient();
        Request request = new Request.Builder()
                .url(getFullUrl(url, params))
                .headers(headersBuilder(requestHeaders))
                .build();
        Response response = client.newCall(request).execute();
        return (T) new com.bjy.qa.agent.tools.http.Response(request.headers(), response.code(), response.headers(), response.body());
    }

    /**
     * post 异步请求
     * @param url url地址
     * @param callback 回调
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    public void postAsync(String url, Callback callback) throws UnsupportedEncodingException {
        postAsync(url, null, null, null, callback);
    }

    /**
     * post 异步请求
     * @param url url地址
     * @param bodyParams body中的参数
     * @param callback 回调
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    public void postAsync(String url, Params bodyParams, Callback callback) throws UnsupportedEncodingException {
        postAsync(url, null, bodyParams, null, callback);
    }

    /**
     * post 异步请求
     * @param url url地址
     * @param urlParams url 地址中的参数
     * @param bodyParams body中的参数
     * @param callback 回调
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    public void postAsync(String url, Params urlParams, Params bodyParams, Callback callback) throws UnsupportedEncodingException {
        postAsync(url, null, null, null, callback);
    }

    /**
     * post 异步请求
     * @param url url地址
     * @param urlParams url 地址中的参数
     * @param bodyParams body中的参数
     * @param postType 媒体类型，FORM（表单） JSON（数据）
     * @param callback 回调
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    public void postAsync(String url, Params urlParams, Params bodyParams, PostType postType, Callback callback) throws UnsupportedEncodingException {
        OkHttpClient mOkHttpClient = getOkHttpClient();

        Request request;
        switch (postType==null ? this.postType : postType) {
            case JSON:
                request = buildJsonPostRequest(url, urlParams, bodyParams);
                break;
            default: // 默认 FORM 类型 Request
                request = buildFormPostRequest(url, urlParams, bodyParams);
        }
        mOkHttpClient.newCall(request).enqueue(new okhttp3.Callback() {
            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                callback.onResponse(new com.bjy.qa.agent.tools.http.Response(request.headers(), response.code(), response.headers(), response.body()));
            }

            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                callback.onFailure(e);
            }
        });
    }

    /**
     * post 同步请求
     * @param url url地址
     * @return String
     * @throws IOException IOException
     */
    public <T> T post(String url) throws IOException {
        return post(url, null, null, null);
    }

    /**
     * post 同步请求
     * @param url url地址
     * @param bodyParams body中的参数
     * @return String
     * @throws IOException IOException
     */
    public <T> T post(String url, Params bodyParams) throws IOException {
        return post(url, null, bodyParams, null);
    }

    /**
     * post 同步请求
     * @param url url地址
     * @param urlParams url 地址中的参数
     * @param bodyParams body中的参数
     * @return String
     * @throws IOException IOException
     */
    public <T> T post(String url, Params urlParams, Params bodyParams) throws IOException {
        return post(url, urlParams, bodyParams, null);
    }

    /**
     * post 同步请求
     * @param url url地址
     * @param urlParams url 地址中的参数
     * @param bodyParams body中的参数
     * @param postType 媒体类型，FORM（表单） JSON（数据）
     * @return String
     * @throws IOException IOException
     */
    public <T> T post(String url, Params urlParams, Params bodyParams, PostType postType) throws IOException {
        OkHttpClient okHttpClient = getOkHttpClient();

        Request request;
        switch (postType==null ? this.postType : postType) {
            case JSON:
                request = buildJsonPostRequest(url, urlParams, bodyParams);
                break;
            default: // 默认 FORM 类型 Request
                request = buildFormPostRequest(url, urlParams, bodyParams);
        }

        Call call = okHttpClient.newCall(request);
        Response response = call.execute();

        return (T) new com.bjy.qa.agent.tools.http.Response(request.headers(), response.code(), response.headers(), response.body());
    }

}
